package com.moyun.syssecurity.filter;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.moyun.model.common.Constants;
import com.moyun.common.utils.JwtTokenUtil;
import com.moyun.common.utils.SpringContextUtil;
import com.moyun.model.po.system.SysUserEntity;
import com.moyun.syssecurity.model.JwtUser;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.logout.CompositeLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @ClassName JwtRequestFilter
 * @Description 用于验证JWT的过滤器
 * @Author yangboxuan
 * @Date 2021/5/4 1:56
 */
@Slf4j
@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    /**
     * 过滤器是最先注入的,此时 JwtTokenUtil还没有被Spring管理
     **/
    private static String ONLINE_USERS = "ONLINE_USERS";
    private LogoutHandler handlers = new CompositeLogoutHandler(new LogoutHandler[]{new SecurityContextLogoutHandler()});


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        log.info("进入到jwt 过滤器中");
        JwtTokenUtil jwtTokenUtil = SpringContextUtil.getBean(JwtTokenUtil.class);
        ObjectMapper objectMapper = SpringContextUtil.getBean(ObjectMapper.class);
        RedisTemplate redisTemplate = SpringContextUtil.getBean("redisTemplate", RedisTemplate.class);
        String username = null;

        String jwtToken = getJwtToken(request);

        try {
            username = jwtTokenUtil.getUsernameFromToken(jwtToken);
        } catch (IllegalArgumentException e) {
            log.info("未获取到jwt令牌");
        } catch (ExpiredJwtException e) {
            log.info("jwt令牌已经过期了");
        } catch (Exception e) {
            log.info("未能获取到jwt令牌");
        }

        // 2022.5.16 新增功能 从redis中获取当前用户的token,判断token是否还在有效期
        String requestURI = request.getRequestURI();
        String cacheJwtToken = (String) redisTemplate.opsForValue().get("ONLINE_USERS" + username);
        if (cacheJwtToken != null && !"/login".equals(requestURI)) {
            // 证明系统已经为 当前用户分配过 会话token 并且 当前并不是登录请求  用户是携带token想访问系统
            if (!Objects.equals(cacheJwtToken, jwtToken)) {
                System.out.println("用户持有的当前token已经失效,需要当前用户重新登录分配新的token使用");
                this.doLogout(request, response);
//                // redis 存储的用户 jwtToken 和 用于传递过来的 jwtToken 不一致,说明 用户传递的 jwtToken 已经失效
//                response.setContentType("application/json;charset=UTF-8");
//                PrintWriter out = response.getWriter();
//                out.print("{\"code\":\"1\",\"msg\":\" 您的登录状态已过期(账号已在另一台设备登录).如有需要,请重新登录! \"}");
//                out.flush();
//                out.close();
//                return;
            } else {
                // 验证token
                boolean setAuthentication = username != null && SecurityContextHolder.getContext().getAuthentication() == null && jwtTokenUtil.validateToken(jwtToken, username);
                if (setAuthentication) {
                    // 如果token有效，则进行手动授权并且将其设置到Spring Security的上下文中  这里体现了泛型的好处
                    String userJson = jwtTokenUtil.getClaimFromToken(jwtToken, (c) -> c.get(Constants.PRINCIPAL, String.class));
                    JwtUser jwtUser = objectMapper.readValue(userJson, JwtUser.class);
//                    User userDetails = new User(jwtUser.getUsername(), jwtUser.getPassword(), jwtUser.getAuthorities().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
                    SysUserEntity sysUserEntity = new SysUserEntity();
                    sysUserEntity.setId(jwtUser.getUserId());
                    sysUserEntity.setUserName(jwtUser.getUsername());
                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(sysUserEntity, null, jwtUser.getAuthorities().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
                    usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                }
            }
        }

        filterChain.doFilter(request, response);
    }

    private String getJwtToken(HttpServletRequest request) {
        String jwtToken = null;
        String requestTokenHeader = request.getHeader("Authorization");
        // JWT一般是"Bearer token"这样的格式，去除Bearer 前缀，只留下token本身
        String bearerPrefix = "Bearer ";
        if (requestTokenHeader != null && requestTokenHeader.startsWith(bearerPrefix)) {
            jwtToken = requestTokenHeader.substring(7);
            log.info("从 请求头<Authorization>属性 获取到jwtToken");
            return jwtToken;
        }
        if (request.getHeader("jwtToken") != null) {
            log.info("从 请求头<jwtToken>属性 获取到jwtToken");
            return request.getHeader("jwtToken");
        }
        if (request.getCookies() != null) {
            for (Cookie cookie : request.getCookies()) {
//                if (Constants.JWT.equals(cookie.getName())) {
                if ("jwtToken".equals(cookie.getName())) {
                    log.info("从 Cookie<jwtToken>属性 获取到jwtToken");
                    jwtToken = cookie.getValue();
                }
            }
        }


        return jwtToken;
    }


    private void doLogout(HttpServletRequest request, HttpServletResponse response) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        this.handlers.logout(request, response, auth);
    }
}
